背景
仕事でWebUIでアプリを作ることになり、いろいろ勉強中です。
偶然、このブログを作っていた事が色々役に立ちました。
まぁそんなこんなで、コンポーネントをまたいでやり取りするケースが出てきて、
いろいろ考えたわけです。
当方、C#が今のところ一番得意なようで、
もしC#で組むならDIコンテナ✕イベントでやるなと。
ということで、TSでもDIコンテナ✕イベントをやる方法を模索しました。
ホントはMessagePipeのようにDIコンテナの
セットアップが簡単にできるやつがあったら良かったんですが、
見つけられなかった(ぶっちゃけそんなにガチに探してないです、あったら教えてくださいm(_ _)m)
ので、nanoeventsとinversifyを組み合わせて見ることにしました。
※当方はtypescriptやweb系技術についてズブの素人です。ご注意ください。
結論(ソース)
Githubに置きました
Dockerで環境構築してください。
Dockerで環境構築する際はぜひこの記事も参考にしてみてください!
また、お初ですが、devcontainer.json
を設置してあるので、
Github Codespaceを使うとセットアップが自動化されます。
便利な世の中だなぁ...(しみじみ
詳解
DIコンテナとnanoeventsの導入
依存解決
- inversifyを導入(記事いっぱいあるため割愛)
- nanoeventsを導入
yarn add nanoevents
イベントをハンドリングするインターフェースとクラスの準備
- 新規に
IHogeEventProvider.tsx
を作成する
export interface CallbackFunc {
(push: boolean): void;
}
export interface IHogeEventProvider{
publish: (push:boolean) => void,
subscribe: (cbFunc:CallbackFunc) => void
}
- 新規に
IHogeEvent.tsx
を作成する
export interface IHogeEvent {
push: (push: boolean) => void;
}
- 新規に
HogeEventProvider.tsx
を作成する
import { CallbackFunc, IHogeEventProvider } from "@/interfaces/IHogeEventProvider"
import { IHogeEvent } from "@/interfaces/eventSchemas/IHogeEvent"
import { injectable } from "inversify";
import { Emitter, createNanoEvents } from "nanoevents"
import 'reflect-metadata'
@injectable()
export class HogeEventProvider implements IHogeEventProvider {
private emitter: Emitter<IHogeEvent>;
constructor(){
this.emitter = createNanoEvents<IHogeEvent>();
}
public publish(push: boolean) {
this.emitter.emit('push',push);
}
public subscribe(cbFunc: CallbackFunc) {
this.emitter.on('push',push => {
cbFunc(push);
})
}
}
DIコンテナのセットアップ
- tsconfig.jsonを修正
"experimentalDecorators": true
と"emitDecoratorMetadata": true
を追加
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"experimentalDecorators": true, "emitDecoratorMetadata": true, "plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
- next.config.jsを修正
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
instrumentationHook: true
}
}
module.exports = nextConfig
- 新規に
Types.ts
を作成する
この中にはDIコンテナに登録するための型情報リストが格納されると思ってもらって良い
import { IHogeEventProvider } from "@/interfaces/IHogeEventProvider"
const TYPES = {
IHogeEventProvider: Symbol.for('IHogeEventProvider'),
}
export { TYPES }
- 新規に
inversify.config.ts
を作成する
この中で、DIコンテナの登録処理を行う
import { Container } from 'inversify'
import { TYPES } from '@/dependencyInjections/Types'
import { IHogeEventProvider } from '@/interfaces/IHogeEventProvider'
import { HogeEventProvider } from '@/implements/HogeEventProvider'
const myContainer = new Container()
myContainer.bind<IHogeEventProvider>(TYPES.IHogeEventProvider).to(HogeEventProvider).inSingletonScope()
export { myContainer }
DIコンテナとイベントの呼び出し方・使い方
Subscriber
例として、LeftComponent.tsxで示す
import React, { useState } from "react";
import { myContainer } from "@/dependencyInjections/inversify.config";
import { HogeEventProvider } from "@/implements/HogeEventProvider";
import { TYPES } from "@/dependencyInjections/Types";
type Props = {};
const LeftComponent = (props: Props) => {
const eventAction = myContainer.get<HogeEventProvider>(TYPES.IHogeEventProvider); const [isOn, setIsOn] = useState(false);
eventAction.subscribe(x => { setIsOn(!isOn); });
return (
<>
ひだりのこんぽーねんと<br/>
{isOn ? "おん" : "おっふ"}<br/>
<br/>
</>
);
};
export default LeftComponent;
8行目でDIコンテナからEventProviderを取り出し、
11-13行目でイベント購読のコールバック処理を記述している
Publisher
例として、RightComponent.tsxで示す
import React, { useState } from "react";
import { myContainer } from "@/dependencyInjections/inversify.config";
import { HogeEventProvider } from "@/implements/HogeEventProvider";
import { TYPES } from "@/dependencyInjections/Types";
import { Button } from 'antd';
type Props = {};
const RightComponent = (props: Props) => {
const eventAction = myContainer.get<HogeEventProvider>(TYPES.IHogeEventProvider);
return (
<div>
右のコンポーネント<br/>
<Button type="primary" onClick={x => eventAction.publish(true)} > Button </Button> </div>
);
};
export default RightComponent;
10行目でDIコンテナからEventProviderを取り出し、
15行目でイベントの発信を行っている
実行の様子
RightComponentにあるButtonを押すことで、
LeftComponentの表示が切り替わっているのがわかる
もちろんイベントのため、
複数の一斉購読も可能
〆
DIコンテナから使う側はほぼMessagePipeと同じ使用感になったので
結構満足です(`・ω・´)
しかし、DIコンテナの登録が鬼面倒くさいですね...
これなんとかしたいなぁ...
まぁとりあえずこれで比較的キレイ&楽チンに
コンポーネント間通信ができるようになりましたね!
めでたしめでたし?
では(^^)ノシ